/*
 * Copyright (c) 2010
 *      Toni Spets <toni.spets@iki.fi>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <windows.h>
#include <stdio.h>
#include <wsipx.h>

#include "sockets.h"
#include "config.h"

struct sockaddr_in my_addr;
struct sockaddr_in my_server;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
        config_init();
    }

    return TRUE;
}

WINAPI SOCKET WSAAPI fake_socket(int af, int type, int protocol)
{
    if(af == AF_IPX) {
        SOCKET s = net_socket();
        net_address_ex(&my_addr, INADDR_ANY, config_port);
#if LAN_ONLY
        net_address_ex(&my_server, INADDR_BROADCAST, config_port);
#else
        net_address(&my_server, config_server, config_server_port);
#endif
        net_broadcast(s);
        return s;
    }

    return socket(af, type, protocol);
}

WINAPI int fake_bind(SOCKET s, const struct sockaddr *name, int namelen)
{
    /* force IPX to always bind to anything local, must always succeed (RA/TS/RA2 tests for IPX support by creating an IPX socket and binding) */
    if(((struct sockaddr_ipx *)name)->sa_family == AF_IPX)
    {
        bind(s, (const struct sockaddr *)&my_addr, namelen);
        return 0;
    }
    return bind(s, name, namelen);
}

WINAPI int fake_recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
{
    struct sockaddr_in *tmp = (struct sockaddr_in *)from;
    int ret;
    ra_packet_t packet_t;
    ra_packet_t *packet = &packet_t;

    memset(packet, 0, sizeof(ra_packet_t) - sizeof(ra_buffer_t));

    ret = net_recv(s, (char *)&packet->buffer, sizeof(packet->buffer), tmp);
    if(ret > 0) {
        if(ret < sizeof(ra_client_t))
            return 0;
        ret -= sizeof(ra_client_t) + 1; /* take direct byte into account */
        tmp->sin_family = AF_IPX;
        if(packet->buffer.from.ip != 0) {
            // if specified source address, use it
            tmp->sin_addr.s_addr = packet->buffer.from.ip;
            tmp->sin_port = packet->buffer.from.port;
        }
        memcpy(buf, packet->buffer.data, ret);
        return ret;
    }

    return recvfrom(s, buf, len, flags, from, fromlen);
}

WINAPI int fake_sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen)
{
    struct sockaddr_in  to_in;
    ra_buffer_t         buffer;

    if(to->sa_family == AF_IPX)
    {
        struct sockaddr_ipx *to_ipx = (struct sockaddr_ipx *)to;

        memset(&buffer.from, 0, sizeof(ra_client_t));
        memcpy(&buffer.data, buf, len);

        to_in.sin_family = AF_INET;
        to_in.sin_addr.s_addr = ((struct sockaddr_in *)to)->sin_addr.s_addr;
        to_in.sin_port = ((struct sockaddr_in *)to)->sin_port;

        if(to_ipx->sa_netnum[0] == 0 && to_ipx->sa_netnum[1] == 0 && to_ipx->sa_netnum[2] == 0 && to_ipx->sa_netnum[3] == 0)
        {
            buffer.direct = 0x00;

            if(config_port_force)
            {
                buffer.from.port = htons(config_port);
            }
            
            net_send(s, (char *)&buffer, sizeof(ra_client_t)+len+1, &my_server);
            return len;
        }

        buffer.direct = 0x01;

        net_send(s, (char *)&buffer, sizeof(ra_client_t)+len+1, &to_in);
        return len;
    }

    return sendto(s, buf, len, flags, to, tolen);
}

WINAPI int fake_getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen)
{
    if(level == 0x3E8)
    {
        *optval = 1;
        *optlen = 1;
        return 0;
    }
    if(level == 0xFFFF)
    {
        *optval = 1;
        *optlen = 1;
        return 0;
    }
    return getsockopt(s, level, optname, optval, optlen);
}

WINAPI int fake_setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
{
    /* ignore IPX level sets */
    if(level == 0x3E8)
    {
        return 0;
    }
    if(level == 0xFFFF)
    {
        return 0;
    }
    return setsockopt(s, level, optname, optval, optlen);
}
